package com.uuah.server.message;

import java.io.InputStream;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.uuah.server.UuahServer;
import com.uuah.server.api.IExceptionPayload;
import com.uuah.server.api.IUuahMessage;
import com.uuah.server.api.IThreadSafeAccess;
import com.uuah.server.api.transformer.IBaseTransformer;
import com.uuah.server.api.transport.IMessageAdapter;
import com.uuah.server.config.i18n.ServerMessages;
import com.uuah.server.exception.transformer.TransformerException;
import com.uuah.server.transport.AbstractMessageAdapter;
import com.uuah.server.transport.MessageAdapter;
import com.uuah.server.transport.NullPayload;
import com.uuah.server.transport.PropertyScope;
import com.uuah.utils.ClassUtils;

/**
 *
 * TODO comment ...
 *
 * <p>
 * update record:updator，update time，update content and version
 * </p>
 *
 * @author <a href="jonny_quan@hotmail.com">jonny</a>
 * @date 2009 2009-3-27 下午02:02:45
 * @version 1.0.0
 */
public class UuahMessage implements IUuahMessage, IThreadSafeAccess {
	private static final long serialVersionUID = 1541720810851984842L;
	private static transient Logger logger = LoggerFactory
			.getLogger(UuahMessage.class);

	private IMessageAdapter adapter;
	private IMessageAdapter originalAdapter = null;
	private transient List appliedTransformerHashCodes = new CopyOnWriteArrayList();
	private byte[] cache;

	private static final List consumableClasses = new ArrayList();

	static {
		try {
			consumableClasses.add(ClassUtils.loadClass(
					"javax.xml.stream.XMLStreamReader", UuahMessage.class));
		} catch (ClassNotFoundException e) {
		}
	}

	public UuahMessage(Object message) {
		this(message, (Map) null);
	}

	public UuahMessage(Object message, Map properties) {
		if (message instanceof IMessageAdapter) {
			adapter = (IMessageAdapter) message;
		} else {
			adapter = new MessageAdapter(message);
		}
		addProperties(properties);
		resetAccessControl();
	}

	public UuahMessage(Object message, IMessageAdapter previous) {
		if (message instanceof IMessageAdapter) {
			adapter = (IMessageAdapter) message;
			((IThreadSafeAccess) adapter).resetAccessControl();
		} else {
			adapter = new MessageAdapter(message, previous);
		}
		if (previous.getExceptionPayload() != null) {
			setExceptionPayload(previous.getExceptionPayload());
		}
		setEncoding(previous.getEncoding());

		resetAccessControl();
	}

	public Object getPayload(Class outputType) throws TransformerException {
		return getPayload(outputType, getEncoding());
	}

	protected Object getPayload(Class outputType, String encoding)
			throws TransformerException {
		if (outputType == null) {
			return getPayload();
		}

		Class inputCls = getPayload().getClass();

		if (Proxy.isProxyClass(inputCls)) {
			inputCls = inputCls.getInterfaces()[0];
		}

		if (outputType.isAssignableFrom(inputCls)) {
			return getPayload();
		}

		IBaseTransformer transformer = null;
		transformer = UuahServer.getUuahContext().getRegistry()
				.lookupTransformer(inputCls, outputType);

		// no transformers found
		if (transformer == null) {
			throw new TransformerException(ServerMessages
					.noTransformerFoundForMessage(inputCls, outputType));
		}

		Object result = transformer.transform(this);

		if (!outputType.isAssignableFrom(result.getClass())) {
			throw new TransformerException(ServerMessages
					.transformOnObjectNotOfSpecifiedType(outputType.getName(),
							result.getClass()));
		}

		if (isPayloadConsumed(inputCls)) {
			setPayload(result);
		}

		return result;
	}

	protected boolean isPayloadConsumed(Class inputCls) {
		return InputStream.class.isAssignableFrom(inputCls)
				|| isConsumedFromAdditional(inputCls);
	}

	private boolean isConsumedFromAdditional(Class inputCls) {
		if (consumableClasses.isEmpty()) {
			return false;
		}

		for (Iterator itr = consumableClasses.iterator(); itr.hasNext();) {
			Class c = (Class) itr.next();

			if (c.isAssignableFrom(inputCls)) {
				return true;
			}
		}
		return false;
	}

	public IMessageAdapter getAdapter() {
		return adapter;
	}

	public Object getOrginalPayload() {
		return (originalAdapter == null ? adapter.getPayload()
				: originalAdapter.getPayload());
	}

	public IMessageAdapter getOriginalAdapter() {
		return (originalAdapter == null ? adapter : originalAdapter);
	}

	public void setProperty(String key, Object value, PropertyScope scope) {
		adapter.setProperty(key, value, scope);
	}

	public Object getProperty(String key) {
		return adapter.getProperty(key);
	}

	public Object removeProperty(String key) {
		return adapter.removeProperty(key);
	}

	public void setProperty(String key, Object value) {
		adapter.setProperty(key, value);
	}

	public final String getPayloadAsString() throws Exception {
		assertAccess(READ);
		return getPayloadAsString(getEncoding());
	}

	public byte[] getPayloadAsBytes() throws Exception {
		assertAccess(READ);
		if (cache != null) {
			return cache;
		}
		byte[] result = (byte[]) getPayload(byte[].class);
		if (UuahServer.getUuahContext().getConfiguration()
				.isCacheMessageAsBytes()) {
			cache = result;
		}
		return result;
	}

	public String getPayloadAsString(String encoding) throws Exception {
		assertAccess(READ);
		if (cache != null) {
			return new String(cache, encoding);
		}
		String result = (String) getPayload(String.class);
		if (UuahServer.getUuahContext().getConfiguration()
				.isCacheMessageAsBytes()) {
			cache = result.getBytes(encoding);
		}
		return result;
	}

	public Set getPropertyNames() {
		return adapter.getPropertyNames();
	}

	public double getDoubleProperty(String name, double defaultValue) {
		return adapter.getDoubleProperty(name, defaultValue);
	}

	public void setDoubleProperty(String name, double value) {
		adapter.setDoubleProperty(name, value);
	}

	public String getUniqueId() {
		return adapter.getUniqueId();
	}

	public Object getProperty(String name, Object defaultValue) {
		return adapter.getProperty(name, defaultValue);
	}

	public int getIntProperty(String name, int defaultValue) {
		return adapter.getIntProperty(name, defaultValue);
	}

	public long getLongProperty(String name, long defaultValue) {
		return adapter.getLongProperty(name, defaultValue);
	}

	public boolean getBooleanProperty(String name, boolean defaultValue) {
		return adapter.getBooleanProperty(name, defaultValue);
	}

	public void setBooleanProperty(String name, boolean value) {
		adapter.setBooleanProperty(name, value);
	}

	public void setIntProperty(String name, int value) {
		adapter.setIntProperty(name, value);
	}

	public void setLongProperty(String name, long value) {
		adapter.setLongProperty(name, value);
	}

	public IExceptionPayload getExceptionPayload() {
		return adapter.getExceptionPayload();
	}

	public void setExceptionPayload(IExceptionPayload exceptionPayload) {
		adapter.setExceptionPayload(exceptionPayload);
	}

	public String toString() {
		return adapter.toString();
	}

	public String getEncoding() {
		return adapter.getEncoding();
	}

	public void setEncoding(String encoding) {
		adapter.setEncoding(encoding);
	}

	public String getStringProperty(String name, String defaultValue) {
		return adapter.getStringProperty(name, defaultValue);
	}

	public void setStringProperty(String name, String value) {
		adapter.setStringProperty(name, value);
	}

	public void addProperties(Map properties) {
		adapter.addProperties(properties);
	}

	public void addProperties(Map properties, PropertyScope scope) {
		adapter.addProperties(properties, scope);
	}

	public void clearProperties() {
		adapter.clearProperties();
	}

	public Object getPayload() {
		return adapter.getPayload();
	}

	public synchronized void setPayload(Object payload) {
		if (!(adapter instanceof IMessageAdapter)) {
			adapter = new MessageAdapter(payload, adapter);
		} else {
			((IMessageAdapter) adapter).setPayload(payload);
		}
		cache = null;
	}

	public void release() {
		adapter.release();
		if (originalAdapter != null) {
			originalAdapter.release();
		}
		cache = null;
		appliedTransformerHashCodes.clear();
	}

	public void applyTransformer(IBaseTransformer transformer)
			throws TransformerException {
		applyTransformer(transformer, null);
	}

	public void applyTransformer(IBaseTransformer transformer, Class outputType)
			throws TransformerException {
		if (transformer != null
				&& !appliedTransformerHashCodes.contains(new Integer(
						transformer.hashCode()))) {
			if (getPayload() == null) {
				if (transformer.isAcceptNull()) {
					setPayload(NullPayload.getInstance());
				} else {
					if (logger.isDebugEnabled()) {
						logger
								.debug("Transformer "
										+ transformer
										+ " doesn't support the null payload, exiting from transformer chain.");
					}
				}
			}

			Class srcCls = getPayload().getClass();
			if (transformer.isSourceTypeSupported(srcCls)) {
				Object result = transformer.transform(this);

				if (originalAdapter == null
						&& UuahServer.getUuahContext().getConfiguration()
								.isCacheMessageOriginalPayload()) {
					originalAdapter = adapter;
				}
				if (result instanceof IUuahMessage) {
					synchronized (this) {
						adapter = ((IUuahMessage) result).getAdapter();
					}
				} else {
					setPayload(result);
				}
			} else {
				if (logger.isDebugEnabled()) {
					logger.debug("Transformer " + transformer
							+ " doesn't support the source payload: " + srcCls);
				}
			}

			appliedTransformerHashCodes
					.add(new Integer(transformer.hashCode()));
		}

		if (null != outputType
				&& !getPayload().getClass().isAssignableFrom(outputType)) {
			setPayload(getPayload(outputType));
		}
	}

	public IThreadSafeAccess newThreadCopy() {
		if (adapter instanceof IThreadSafeAccess) {
			if (logger.isDebugEnabled()) {
				logger.debug("new copy of message for "
						+ Thread.currentThread());
			}
			return new UuahMessage(
					((IThreadSafeAccess) adapter).newThreadCopy(), this);
		} else {
			return this;
		}
	}

	public void resetAccessControl() {
		if (adapter instanceof AbstractMessageAdapter) {
			((AbstractMessageAdapter) adapter).resetAccessControl();
		}
		if (originalAdapter instanceof AbstractMessageAdapter) {
			((AbstractMessageAdapter) originalAdapter).resetAccessControl();
		}
	}

	public void assertAccess(boolean write) {
		if (adapter instanceof AbstractMessageAdapter) {
			((AbstractMessageAdapter) adapter).assertAccess(write);
		}
	}

	public Map getScopeProperties(PropertyScope scope) {
		return adapter.getScopeProperties(scope);
	}
}
